package com.ctsi.framework.mybatis.interceptor;

import com.ctsi.framework.mybatis.dialect.Dialect;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Mybatis数据库分页插件.
 * @Signature 注解 type：指定要拦截的对象；method：指明要拦截该类的那个方法；args：指明要拦截的方法参数集合
 * @author zhangjw
 *
 */
@Component
@Intercepts({@Signature(method="prepare",type= StatementHandler.class,args={Connection.class,Integer.class}),
			@Signature(method="parameterize",type= StatementHandler.class,args={Statement.class})})
public class PaginaTionInerceptor implements Interceptor, Serializable {
	private static final long serialVersionUID = -6017986684961521912L;
	private static final Logger logger = LoggerFactory.getLogger(PaginaTionInerceptor.class);
	@Autowired
	private Dialect dialect;
	/**
	 * 实现拦截器的逻辑
	 * 内部主要通过invocation.proceed()显示地推进责任链前进，也就是调用下一个拦截器拦截目标方法
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		
		Method m = invocation.getMethod();
		if("prepare".equals(m.getName())){
			return prepare(invocation);
		}else if("parameterize".equals(m.getName())){
			return paramterize(invocation);
		}
		
		return invocation.proceed();
	}

	private Object paramterize(Invocation invocation) throws Throwable {

		Statement statement = (Statement) invocation.getArgs()[0];
		
		if(statement instanceof PreparedStatement){
			PreparedStatement ps = (PreparedStatement) statement;
			
			StatementHandler statementHandler = getStatementHandler(invocation);
			RowBounds rowBounds = getRoeBounds(statementHandler);
			
			if(hasBounds(rowBounds)){
				BoundSql boundSql = statementHandler.getBoundSql();
				List<ParameterMapping> params=boundSql.getParameterMappings();
				int parameterSize = params.size();
				
				dialect.setLimitParamters(ps, parameterSize, rowBounds.getOffset(), rowBounds.getLimit());
				if(dialect.supportsLimitOffset()){
					FieldUtils.setFieldValue(rowBounds,"offset",RowBounds.NO_ROW_OFFSET);
					FieldUtils.setFieldValue(rowBounds,"limit",RowBounds.NO_ROW_LIMIT);
					
					if(dialect.bindLimitParametersFirst()){
						Configuration temcon=new Configuration();
						List<ParameterMapping> nparams=new ArrayList<>(parameterSize+2);
						//分页参数在前
						if (rowBounds.getOffset() > 0) {
							ParameterMapping tem1=new ParameterMapping.Builder(temcon,"_limit",Integer.class).build();	
							ParameterMapping tem2=new ParameterMapping.Builder(temcon,"_offset",Integer.class).build();	
							nparams.add(0, tem1);	
							nparams.add(1, tem2);
							boundSql.setAdditionalParameter("_limit", rowBounds.getLimit());
							boundSql.setAdditionalParameter("_offset", rowBounds.getOffset());
						}else{
							ParameterMapping tem=new ParameterMapping.Builder(temcon,"_limit",Integer.class).build();	
							nparams.add(0, tem);
							boundSql.setAdditionalParameter("_limit", rowBounds.getLimit());
						}
						nparams.addAll(params);
						FieldUtils.setFieldValue(boundSql,"parameterMappings",nparams);
					}					
				}else{
					FieldUtils.setFieldValue(rowBounds,"limit",RowBounds.NO_ROW_LIMIT);
				}

				
			}
		}
		
		return invocation.proceed();
	}

	private Object prepare(Invocation invocation) throws Throwable{
		StatementHandler statement = getStatementHandler(invocation);
		if( statement instanceof PreparedStatementHandler){
			RowBounds rowBounds = getRoeBounds(statement);
			if(hasBounds(rowBounds)){
				BoundSql boundSql = statement.getBoundSql();
				String sql = boundSql.getSql();
		 
					if(dialect.supportsLimitOffset()){
						sql = dialect.getLimitString(sql, rowBounds.getOffset());
					}else{
						//top 格式查询时需要查询在多少行开始和截取的条数的和
						sql = dialect.getLimitString(sql, rowBounds.getOffset()+ rowBounds.getLimit());
					}
					
					logger.debug("生成的分页SQL为：{}" ,sql);
			 
				FieldUtils.setFieldValue(boundSql,"sql",sql);
			}
		}
		return invocation.proceed();
	}
	
	/**
	 * 用当前拦截器生成目标target的代理，实际是通过Plugin.plugin(Object target)类完成的，把目标和拦截器this传给了包装函数
	 */
	@Override
	public Object plugin(Object target) {
		
		return Plugin.wrap(target, this);
	}

	/**
	 * 用于设置额外的参数，参数配置在拦截器Properties节点里面
	 */
	@Override
	public void setProperties(Properties properties) {
		
	}
	
	
	protected StatementHandler getStatementHandler(Invocation invocation) {
		StatementHandler statement = (StatementHandler) invocation.getTarget();
		if(statement instanceof RoutingStatementHandler){
			statement = (StatementHandler) FieldUtils.getFieldValue(statement, "delegate");
		}
		return statement;
	}
	
	protected RowBounds getRoeBounds(StatementHandler statement) {
		return (RowBounds) FieldUtils.getFieldValue(statement, "rowBounds");
	}
	
	protected boolean hasBounds(RowBounds rowBounds) {
		
		return (rowBounds != null && rowBounds.getLimit() > 0 && rowBounds.getOffset() >= 0 && rowBounds.getLimit() != RowBounds.NO_ROW_LIMIT);
	}

}
